/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

#define ATTR_KEY 1
#define ATTR_VALUE 2
#define ATTR_STAR 3
#define ATTR_COUNT_STAR 4

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);

// Create an instance of the BTreeIndex class object
// Using 'new' to invoke constructor
//BTreeIndex *indexTable = new BTreeIndex();

RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count = 0;
  int    diff;

	bool isIndex = false; // True if an index for table exists;
	bool isKey = false; // True if there is at least one key attr to compare
	bool le = false;
	bool ge = false;
	int lowerBound = -1;
	int upperBound = -1;
	int eqCondCount = 0;
	vector<SelCond> condUseIndex; // Store conditions to be used by index
	vector<SelCond> condNotUseIndex; // Store conditions not used by index
	
	BTreeIndex *indexTable = new BTreeIndex();
	
	// Check if index for this table exists
	rc = indexTable->open(table + ".idx", 'r');
	if (rc == 0)
	{
		// Table exists
		isIndex = true;
	}
	
	// Try to utilize the index
	if (isIndex)
	{
		// check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++)
		{
			// Only care about the 'key' conditions
			if (cond[i].attr == ATTR_KEY)
			{
				int value; // To store the value of the condition
				
				switch (cond[i].comp)
				{
				case SelCond::EQ:
					if (eqCondCount != 0 && eqCondCount != 1)
					{
						// Having more than one equal statement is not correct
						if (attr == ATTR_COUNT_STAR)
						{
							fprintf(stdout, "0\n");
						}
						goto exit_select;
					}
					eqCondCount++;
					condUseIndex.push_back(cond[i]);
					isKey = true;
					break;
				case SelCond::NE:
					condNotUseIndex.push_back(cond[i]);
					break;
				case SelCond::GT:
					value = atoi(cond[i].value);
					if (lowerBound == -1 || value >= lowerBound)
					{
						lowerBound = value;
						ge = false;
					}
					isKey = true;
					break;
				case SelCond::LT:
					value = atoi(cond[i].value);
					if (upperBound == -1 || value <= upperBound)
					{
						upperBound = value;
						le = false;
					}
					isKey = true;
					break;
				case SelCond::GE:
					value = atoi(cond[i].value);
					if (lowerBound == -1 || value > lowerBound)
					{
						lowerBound = value;
						ge = true;
					}
					isKey = true;
					break;
				case SelCond::LE:
					value = atoi(cond[i].value);
					if (upperBound == -1 || value < upperBound)
					{
						upperBound = value;
						le = true;
					}
					isKey = true;
					break;
				default:
					break;
				}
			}
			else
			{
				condNotUseIndex.push_back(cond[i]);
			}
		}
		
		// Now find the information requested
		if (isKey)
		{
			// Check if the conditions given are valid
			if (condUseIndex.size() > 0)
			{
				int value = atoi(condUseIndex[0].value);
				
				bool isError = false; // To check if there was any condition conflicts
				
				// Check with lower bound
				if (ge)
				{
					if (value <= lowerBound)
					{
						isError = true;
					}
				}
				else
				{
					if (lowerBound != -1  && value < lowerBound)
					{
						isError = true;
					}
				}
				
				// Check with upper bound
				if (le)
				{
					if (value >= upperBound)
					{
						isError = true;
					}
				}
				else
				{
					if (upperBound != -1 && value > upperBound)
					{
						isError = true;
					}
				}
				
				if (isError)
				{
					// The key conditions conflicted, so we exit
					if (attr == ATTR_COUNT_STAR)
					{
						fprintf(stdout, "0\n");
					}
					goto exit_select;
				}
				else
				{
					// No conflicts in key condition
					lowerBound = upperBound = value;
					ge = le = true;
				}
			}
			
			// We are now going to locate the entries in the index
			if (lowerBound == -1)
			{
				lowerBound = 0;
			}
			
			// Locate the lower bound value in the tree
			IndexCursor cursor;
			rc = indexTable->locate(lowerBound, cursor);
			if (rc != 0)
			{
				fprintf(stdout, "Error, no such file exists.\n");
				goto exit_select;
			}
			
			int retKey;
			RecordId retRid;
			rc = indexTable->readForward(cursor, retKey, retRid);
			if (rc != RC_END_OF_TREE && rc != 0)
			{
				fprintf(stdout, "Error, could not read file.\n");
				goto exit_select;
			}
			
			// Need to move cursor forward if not ge
			if (retKey == lowerBound && rc == 0 && ge == false)
			{
				rc = indexTable->readForward(cursor, retKey, retRid);
			}
			
			// We need to open the table for reads now
			if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
				fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
				return rc;
			}
			
			// Now read the tuples and output
			bool lastRead = false; // Used for last entry
			while (rc == 0 && (upperBound == -1 || (retKey < upperBound && le == false) ||
																(retKey <= upperBound && le == true)))
			{
	read_entry:
				if (attr != ATTR_KEY && attr != ATTR_COUNT_STAR)
				{
					if ((rc = rf.read(retRid, key, value)) < 0)
					{
						fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
						goto exit_select;
					}
				}
				
				// If there is at least one 'value' condition
				if (condNotUseIndex.size() > 0)
				{
					if (attr == ATTR_KEY || attr == ATTR_COUNT_STAR)
					{
						if ((rc = rf.read(retRid, key, value)) < 0)
						{
							fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
							goto exit_select;
						}
					}
					
					for (unsigned i = 0; i < condNotUseIndex.size(); i++)
					{
						int differ;
						
						// Computing the difference between the tuples
						switch (condNotUseIndex[i].attr)
						{
						case 1:
							differ = key - atoi(condNotUseIndex[i].value);
							break;
						case 2:
							differ = strcmp(value.c_str(), condNotUseIndex[i].value);
							break;
						default:
							break;
						}
						
						// Determine which tuples to skip
						switch (condNotUseIndex[i].comp)
						{
						case SelCond::EQ:
							if (differ != 0)
								goto next_entry;
							break;
						case SelCond::NE:
							if (differ == 0)
								goto next_entry;
							break;
						case SelCond::GT:
							if (differ <= 0)
								goto next_entry;
							break;
						case SelCond::LT:
							if (differ >= 0)
								goto next_entry;
							break;
						case SelCond::GE:
							if (differ < 0)
								goto next_entry;
							break;
						case SelCond::LE:
							if (differ > 0)
								goto next_entry;
							break;
						default:
							break;
						}
					}
				}
				
				count++;
				
				switch (attr)
				{
				case ATTR_KEY:
					fprintf(stdout, "%d\n", retKey);
					break;
				case ATTR_VALUE:
					fprintf(stdout, "%s\n", value.c_str());
					break;
				case ATTR_STAR:
					fprintf(stdout, "%d '%s'\n", key, value.c_str());
					break;
				default:
					break;
				}
				
	next_entry:
				rc = indexTable->readForward(cursor, retKey, retRid);
				if (rc == RC_END_OF_TREE && lastRead == false && condUseIndex.size() != 1)
				{
					lastRead = true;
					goto read_entry;
				}
			}
			
			if (attr == ATTR_COUNT_STAR)
			{
				goto attr_count_star;
			}
			
			goto exit_select;
		}
		else
		{
			// Cannot utilize index
			goto no_tuple;
		}
	}
	else
	{
no_tuple:
		// No index to utilize, just use table
		// open the table file
		if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
			fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
			return rc;
		}
		
		// scan the table file from the beginning
		rid.pid = rid.sid = 0;
		count = 0;
		while (rid < rf.endRid()) {
			// read the tuple
			if ((rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
	
			// check the conditions on the tuple
			for (unsigned i = 0; i < cond.size(); i++) {
				// compute the difference between the tuple value and the condition value
				switch (cond[i].attr) {
				case 1:
					diff = key - atoi(cond[i].value);
					break;
				case 2:
					diff = strcmp(value.c_str(), cond[i].value);
					break;
				}
	
				// skip the tuple if any condition is not met
				switch (cond[i].comp) {
				case SelCond::EQ:
					if (diff != 0) goto next_tuple;
					break;
				case SelCond::NE:
					if (diff == 0) goto next_tuple;
					break;
				case SelCond::GT:
					if (diff <= 0) goto next_tuple;
					break;
				case SelCond::LT:
					if (diff >= 0) goto next_tuple;
					break;
				case SelCond::GE:
					if (diff < 0) goto next_tuple;
					break;
				case SelCond::LE:
					if (diff > 0) goto next_tuple;
					break;
				}
			}
	
			// the condition is met for the tuple. 
			// increase matching tuple counter
			count++;
	
			// print the tuple 
			switch (attr) {
			case 1:  // SELECT key
				fprintf(stdout, "%d\n", key);
				break;
			case 2:  // SELECT value
				fprintf(stdout, "%s\n", value.c_str());
				break;
			case 3:  // SELECT *
				fprintf(stdout, "%d '%s'\n", key, value.c_str());
				break;
			}
	
			// move to the next tuple
			next_tuple:
			++rid;
		}
	}

attr_count_star:
  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
exit_select:
	indexTable->close();
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	string table_name = table + ".tbl"; // Append ".tbl" to table_name
	RecordFile rf;
	rf.open(table_name, 'w'); // Open/Create table in write mode
	
	BTreeIndex *indexTable = new BTreeIndex(); // 'new' to call constructor
	
	// If we are using the B+ Tree
	if (index)
	{
		// Open the index to write data to
		string indexTableName = table + ".idx";
		indexTable->open(indexTableName, 'w');
	}
	
	string cur_line;
	ifstream infile;
	infile.open(loadfile.c_str());
	
	while(!infile.eof() && infile.good())
	{
		// To get you all the lines.
		getline(infile, cur_line); // Saves the line in cur_line.
		
		int key;
		string value;
		
		if (cur_line == "")
		{
			break;
		}
		
		parseLoadLine(cur_line, key, value);
		
		RecordId rid;
		if (rf.append(key, value, rid) != 0)
		{
			printf("Error loading table."); // Error if return value is not 0
			return RC_INVALID_ATTRIBUTE;
		}
		else
		{
			// If index is set to true
			if (index)
			{
				if (indexTable->insert(key, rid) != 0)
				{
					cout << "Error writing to index table." << endl;
					return RC_FILE_WRITE_FAILED;
				}
			}
		}
	}

	if (index)
	{
		indexTable->close(); // Close the index table when finished.
	}
	infile.close(); // Close the loadfile when finished.
	rf.close(); // Close the RecordFile when finished.

  return 0; // Return 0 if no error
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
